﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Reflection;

namespace GameLib {
    public class ModList : IModList {

        private List<IMod> mods = new List<IMod>();

        IMod this[int index] {
            get {
                return mods[index];
            }
            set {
                lock (mods) {
                    mods.Remove(value);
                    mods[index] = value;
                }
            }
        }

        public SortType SortBy {
            get;
            set;
        }

        public String ModClass {
            get;
            set;
        }

        public DateTime Created {
            get;
            private set;
        }

        public int Add(IMod mod) {
            lock (mods) {
                mods.Remove(mod);
                mods.Add(mod);
            }
            return 1;
        }
        
        public void Clear() {
            lock (mods) {
                mods.Clear();
            }
        }

        public IModList Clone() {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, this);
            ms.Flush();
            ms.Position = 0;
            return (IModList) bf.Deserialize(ms);
        }
        
        public bool Contains(IMod mod) {
            return mods.Contains(mod);
        }

        public void CopyTo(Array array, int index) {
            int length = array.Length - index + 1;
            for (int i = 0; i <= length - 1; i++) {
                array.SetValue(mods[i], index + i);
            }
        }
        
        public int Count {
            get { return mods.Count(); }
        }


        public int Export(String textFile) {
            return Export(textFile, false);
        }

        public int Export(String textFile, bool overwrite) {
            if ((!overwrite) && (File.Exists(textFile))) {
                throw new IOException(textFile + " exists and was not overwritten");
            }

            int exported = 0;
            bool append = false;
            append = (overwrite) ? false : true;

            using (StreamWriter sw = new StreamWriter(textFile, append)) {
                using (TextWriter output = TextWriter.Synchronized(sw)) {
                    foreach (IMod mod in mods) {
                        if (exported > 0) {
                            output.WriteLine();
                        }
                        output.Write(Path.GetFileName(mod.File));
                        exported++;
                    }
                }
            }
            return exported;
        }

        public List<IMod> Find(string field, Object value) {
            List<IMod> result = new List<IMod>();
            foreach (IMod mod in mods) {
                Type type = mod.GetType();
                FieldInfo fieldInfo = type.GetField(field);
                if (fieldInfo == null) {
                    throw new MemberAccessException("Cannot locate field " + field);
                }
                if (value.Equals(fieldInfo.GetValue(mod))) {
                    result.Add(mod);
                }
            }
            return result;
        }

        public IEnumerator GetEnumerator() {
            return mods.GetEnumerator();
        }

        public int Import(string dir, string searchPattern, SortType sortBy) {
            lock (mods) {
                if (!Directory.Exists(dir)) {
                    throw new DirectoryNotFoundException("Import directory not found");
                }

                if (ModClass.Length < 1) {
                    ModClass = "Mod";
                }

                Assembly a = Assembly.GetExecutingAssembly();
                Type modType = a.GetType(ModClass);
                if (modType == null) {
                    throw new InvalidModClassException("Mod class " + ModClass + " is not a valid class");
                }

                string[] filePaths = Directory.GetFiles(dir, searchPattern, SearchOption.TopDirectoryOnly);
                int imported = 0;
                foreach (string filePath in filePaths) {
                    IMod mod = (IMod)a.CreateInstance(ModClass);
                    if (mod == null) {
                        throw new InvalidOperationException("Failed to instantiate new " + ModClass + " instance");
                    }
                    mod.File = filePath;
                    mod.Id = Path.GetFileName(filePath);
                    mod.Title = Path.GetFileNameWithoutExtension(filePath);
                    mods.Add(mod);
                    imported++;
                }

                SortMods(sortBy, dir);                

                return imported;
            }
        }

        public int Import(string textFile, SortType sortBy) {
            return Import(textFile, sortBy, null);
        }

        public int Import(string textFile, SortType sortBy, string dir) {
            int imported = 0;
            lock (mods) {
                if (!File.Exists(textFile)) {
                    throw new FileNotFoundException("Import file " + textFile + " not found", textFile);
                }

                if (ModClass.Length < 1) {
                    ModClass = "Mod";
                }

                Assembly a = Assembly.GetExecutingAssembly();
                Type modType = a.GetType(ModClass);
                if (modType == null) {
                    throw new InvalidModClassException("Mod class " + ModClass + " is not a valid class");
                }

                string[] lines = File.ReadAllLines(textFile);
                foreach (string line in lines) {
                    IMod mod = (IMod) a.CreateInstance(ModClass);
                    if (mod == null) {
                        throw new InvalidOperationException("Failed to instantiate new " + ModClass + " instance");
                    }
                    string file = null;
                    if (dir != null) {
                        file = dir + "\\" + line;
                    } else {
                        file = line;
                    }

                    mod.Id = line;
                    mod.Title = Path.GetFileNameWithoutExtension(line);
                    mod.File = file;
                    mods.Add(mod);
                    imported++;
                }

                SortMods(sortBy, dir);
            }

            return imported;
        }

        public int IndexOf(IMod mod) {
            return mods.IndexOf(mod);
        }

        public void Insert(int index, IMod mod) {
            lock (mods) {
                mods.Insert(index, mod);
            }
        }

        public int InsertAfter(IMod mod, IMod otherMod) {
            int index = mods.IndexOf(otherMod);
            Insert(index + 1, mod);
            return index + 1;
        }

        public int InsertBefore(IMod mod, IMod otherMod) {
            int index = mods.IndexOf(otherMod);
            Insert(index - 1, mod);
            return index - 1;
        }

        public bool IsFixedSize {
            get { return false; }
        }

        public bool IsReadOnly {
            get { return false; }
        }

        public bool IsSynchronized {
            get { return true; }
        }

        public void Remove(IMod mod) {
            lock (mods) {
                mods.Remove(mod);
            }
        }

        public void RemoveAt(int index) {
            lock (mods) {
                mods.RemoveAt(index);
            }
        }

        public void SortMods(SortType sortBy) {
            SortMods(sortBy, null);
        }

        public void SortMods(SortType sortBy, string dir) {
            lock (mods) {
                switch (sortBy) {
                    case SortType.FILENAME:
                        mods.Sort(ModFileNameComparer.INSTANCE);
                        break;
                    case SortType.FILENAME_REVERSE:
                        mods.Sort(ModFileNameComparer.INSTANCE_REVERSE);
                        break;
                    case SortType.TIME_MODIFIED:
                        if (dir == null) {
                            mods.Sort(new ModFileModifiedComparer(false));
                        } else {
                            mods.Sort(new ModFileModifiedComparer(dir, false));
                        }
                        break;
                    case SortType.TIME_MODIFIED_REVERSE:
                        if (dir == null) {
                            mods.Sort(new ModFileModifiedComparer(true));
                        } else {
                            mods.Sort(new ModFileModifiedComparer(dir, true));
                        }
                        break;
                }
            }
        }

        public object SyncRoot {
            get { return this; }
        }
    }
}
